package can;

import robocode.AdvancedRobot;
import robocode.BattleEndedEvent;
import robocode.DeathEvent;
import robocode.HitByBulletEvent;
import robocode.HitWallEvent;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.WinEvent;
import java.awt.Color;

/**
 * This robot moves back and forth across the battlefield, targeting enemies and leading them with
 * bullets.
 * 
 * @author Andrea
 * 
 */
public class ConnellBot extends AdvancedRobot {

  /**
   * Determines whether to move forward or backward.
   */
  private int forward = 1;
  private static boolean smartTargeting = true;

  /**
   * run: connellbot's default behavior.
   */
  public void run() {
    setColors(Color.pink, Color.white, Color.white); // body,gun,radar
    setAdjustGunForRobotTurn(true);

    while (true) {
      // setTurnRadarRight(360);
      // setAhead(200 * forward);
      // this.execute();
      ahead(200 * forward);
      turnRadarRight(360);
    }
  }

  /**
   * When a robot is scanned, estimate the location it will be in by the time a bullet reaches it
   * and shoot toward this location.
   * 
   * @param event Details on the ScannedRobot
   */
  @Override
  public void onScannedRobot(ScannedRobotEvent event) {

    // use just enough power to kill the target
    double firePower = 3; // calculateFirePower(event.getEnergy());

    // calculate bearing relative to 0 (instead of relative to my robot's heading)
    double degChange = ((getHeading() + event.getBearing()) + 360) % 360;

    // shoot in front of enemy if they are moving
    if (event.getVelocity() > 0) {
      // more likely to miss, so decrease firePower
      firePower = firePower * 2 / 3;

      // estimate where to shoot at based on enemy's movement
      degChange =
          getEstimatedDegree(degChange, event.getDistance(), event.getHeading(),
              event.getVelocity(), firePower, getX(), getY(), getBattleFieldWidth(),
              getBattleFieldHeight());
    }

    // take current gun heading into account
    degChange = ((360 - getGunHeading()) + degChange) % 360;

    if (degChange <= 180) {
      turnGunRight(degChange);
    }
    else {
      turnGunLeft(360 - degChange);
    }

    fire(firePower);

  }

  /** 
   * Runs away from a bullet hit.
   * 
   * @param event The HitByBulletEvent
   */
  @Override
  public void onHitByBullet(HitByBulletEvent event) {
    setTurnRight(90);
    ahead(250);
  }

  /**
   * Switch directions around after hitting a wall.
   * 
   * @param event The HitWallEvent
   */
  @Override
  public void onHitWall(HitWallEvent event) {
    forward = forward * -1;
  }

  /**
   * Celebrates a victory.
   * 
   * @param event The WinEvent
   */
  @Override
  public void onWin(WinEvent event) {
    setTurnRight(720);
    ahead(500);

    setTurnLeft(720);
    ahead(-500);
  }

  /**
   * Switches algorithms after losing.
   * 
   * @param event The DeathEvent
   */
  @Override
  public void onDeath(DeathEvent event) {
    setSmartTargeting(!smartTargeting);
  }

  /**
   * Resets the algorithm after the battle is over.
   * 
   * @param event The BattleEndedEvent
   */
  @Override
  public void onBattleEnded(BattleEndedEvent event) {
    setSmartTargeting(true);
  }
  
  /**
   * Updates the static variable smartTargeting.
   * 
   * @param smartTargeting The new value for smartTargeting
   */
  public static void setSmartTargeting(boolean smartTargeting) {
    ConnellBot.smartTargeting = smartTargeting; 
  }
  
  /**
   * Determines the amount of fire power needed to kill the enemy, to avoid wasting energy.
   * 
   * @param energy The amount of energy the enemy has left
   * @return The value to use for fire power
   */
  double calculateFirePower(double energy) {
    double firePower = Math.min((energy + 2) / 6, Rules.MAX_BULLET_POWER);
    if (firePower <= 1) {
      firePower = energy / 4;
    }
    return firePower;
  }

  /**
   * 
   * Given the current position and movement of the enemy, determine where to aim so the bullet will
   * hit them.
   * 
   * @param degChange The current position of the enemy, in degrees relative to my zero
   * @param distance How far the enemy currently is from my robot
   * @param heading Which direction the enemy currently is moving, in degrees 0-359.9
   * @param velocity How fast the enemy currently is moving
   * @param firePower The amount of power that will be used for bullet
   * @param myX My robot's current X-coordinate
   * @param myY My robot's current Y-coordinate
   * @param width The width of the battlefield
   * @param height The height of the battlefield
   * @return The estimated position of the enemy, in degrees relative to my zero.
   */
  double getEstimatedDegree(double degChange, double distance, double heading, double velocity,
      double firePower, double myX, double myY, double width, double height) {

    // determine the enemy's position at the time of sighting
    ConnellBotPoint enemyCurrent = determinePosition(degChange, distance, myX, myY);

    double estimatedDistance = distance;
    boolean unacceptableError = true;
    int i = 0;
    double xDist = 0;
    double yDist = 0;

    do {
      double turnsNeeded = estimatedDistance / Rules.getBulletSpeed(firePower);

      // now estimate the position to shoot based on heading and velocity
      ConnellBotPoint enemyEstimate =
          determinePosition(heading, velocity * turnsNeeded, enemyCurrent.getX(),
              enemyCurrent.getY());

      if (enemyEstimate.getX() < 0) {
        enemyEstimate.setX(0);
      }
      else if (enemyEstimate.getX() > width) {
        enemyEstimate.setX(width);
      }
      if (enemyEstimate.getY() < 0) {
        enemyEstimate.setY(0);
      }
      else if (enemyEstimate.getY() > height) {
        enemyEstimate.setY(height);
      }

      // now form right triangle between my position and the enemy's estimated new position
      xDist = enemyEstimate.getX() - myX;
      yDist = enemyEstimate.getY() - myY;

      double hypot = Math.hypot(xDist, yDist);
      if (hypot > estimatedDistance - 10 && hypot < estimatedDistance + 10) {
        unacceptableError = false;
      }
      else {
        estimatedDistance = hypot;
      }
      i++;
    }
    while (unacceptableError && i < 5 && smartTargeting);

    double estDegChange = 0;

    if (xDist == 0 && yDist == 0) {
      estDegChange = heading;
    }
    else if (xDist == 0 && yDist < 0) {
      estDegChange = 180;
    }
    else if (xDist == 0) { // && yDist > 0) {
      estDegChange = 0;
    }
    else if (yDist == 0 && xDist > 0) {
      estDegChange = 90;
    }
    else if (yDist == 0) { // && xDist < 0) {
      estDegChange = 270;
    }
    else {
      estDegChange = Math.toDegrees(Math.atan(Math.abs(yDist / xDist)));

      // I'm in quadrant 1: point southwest
      if (xDist < 0 && yDist < 0) {
        estDegChange = 270 - estDegChange;
      }
      // I'm in quadrant 2: point northwest
      else if (xDist < 0) { // && yDist > 0
        estDegChange = 270 + estDegChange;
      }
      // I'm in quadrant 3: point northeast
      else if (yDist > 0) { // && xDist > 0
        estDegChange = 90 - estDegChange;
      }
      // I'm in quadrant 4: point southeast
      else { // if (xDist > 0 && yDist < 0)
        estDegChange = 90 + estDegChange;
      }
    }

    return estDegChange;
  }

  /**
   * Given a current position and an angle and distance to the destination, returns the coordinates
   * of the destination.
   * 
   * @param degrees Angle between origin and destination
   * @param distance Length of the hypoteneuse between origin and destination
   * @param xOrigin X-coordinate of the origin
   * @param yOrigin Y-coordinate of the origin
   * @return ConnellBotPoint with X and Y coordinate of the destination
   */
  ConnellBotPoint determinePosition(double degrees, double distance, double xOrigin, 
      double yOrigin) {
    double radians = Math.toRadians(degrees % 90);
    double destX = xOrigin;
    double destY = yOrigin;

    if (degrees == 0) {
      destY = yOrigin + distance;
    }
    if (degrees < 90) {
      destX = (xOrigin + Math.sin(radians) * distance);
      destY = (yOrigin + Math.cos(radians) * distance);
    }
    else if (degrees == 90) {
      destX = (xOrigin + distance);
    }
    else if (degrees < 180) {
      destX = (xOrigin + Math.cos(radians) * distance);
      destY = (yOrigin - Math.sin(radians) * distance);
    }
    else if (degrees == 180) {
      destY = (yOrigin - distance);
    }
    else if (degrees < 270) {
      destX = (xOrigin - Math.sin(radians) * distance);
      destY = (yOrigin - Math.cos(radians) * distance);
    }
    else if (degrees == 270) {
      destX = (xOrigin - distance);
    }
    else {
      destX = (xOrigin - Math.cos(radians) * distance);
      destY = (yOrigin + Math.sin(radians) * distance);
    }

    return new ConnellBotPoint(destX, destY);
  }

}
